﻿using System;
using System.Data;
using System.Data.Common;
using DbUp.Engine.Output;

namespace DbUp.PgSql
{
   /// <summary>
   /// Helps manage databases in PostgreSQL.
   /// </summary>
   public class PgSqlDatabase
   {
      private readonly IUpgradeLog _log;
      private readonly Func<string,IDbConnection> connectionFactory;

      /// <summary>
      /// Initializes a new instance of the <see cref="PgSqlDatabase"/> class.
      /// </summary>
      /// <param name="log">The log.</param>
      /// <param name="connectionFactory">The connection factory.</param>
      public PgSqlDatabase( IUpgradeLog log, Func<string,IDbConnection> connectionFactory )
      {
         if ( log == null ) throw new ArgumentNullException( "log" );
         if ( connectionFactory == null ) throw new ArgumentNullException( "connectionFactory" );
         _log = log;
         this.connectionFactory = connectionFactory;
      }

      /// <summary>
      /// Drops a database.
      /// </summary>
      /// <param name="connectionString">The database connection string.</param>
      /// <param name="database">The name of the database to drop or <c>null</c> to use the database from the connection string.</param>
      public void Drop( string connectionString, string database = null )
      {
         var builder = new DbConnectionStringBuilder { ConnectionString = connectionString };
         var databaseName = database ?? GetDatabaseFromConnectionString( builder );
         builder["database"] = "template1";

         using ( var connection = connectionFactory( builder.ConnectionString ) )
         {
            connection.Open();
            if ( DatabaseExists( connection, databaseName ) )
            {
               _log.WriteInformation( "Dropping database '{0}'", databaseName );
               var command = connection.CreateCommand();
               command.CommandType = CommandType.Text;
               command.CommandText = String.Format( "drop database \"{0}\"", databaseName );
               command.ExecuteNonQuery();
            }
            else
            {
               _log.WriteInformation( "Not dropping database '{0}', it doesn't exist.", databaseName );
            }
         }
      }

      private bool DatabaseExists( IDbConnection connection, string database )
      {
         var command = connection.CreateCommand();
         command.CommandType = CommandType.Text;
         command.CommandText = String.Format( "select count(*) from pg_catalog.pg_database where datname = '{0}';", database );

         return Convert.ToInt64( command.ExecuteScalar() ) > 0;
      }

      /// <summary>
      /// Creates a database.
      /// </summary>
      /// <param name="connectionString">The connection string.</param>
      /// <param name="database">The name of the database to drop or <c>null</c> to use the database from the connection string.</param>
      public void Create( string connectionString, string database = null )
      {
         var builder = new DbConnectionStringBuilder { ConnectionString = connectionString };
         var databaseName = database ?? GetDatabaseFromConnectionString( builder );

         builder["database"] = "template1";

         using ( var connection = connectionFactory( builder.ConnectionString ) )
         {
            connection.Open();
            if ( !DatabaseExists( connection, databaseName ) )
            {
               _log.WriteInformation( "Creating database '{0}'", databaseName );
               var command = connection.CreateCommand();
               command.CommandType = CommandType.Text;
               command.CommandText = String.Format(
@"create database ""{0}"" WITH
       ENCODING = 'UTF8'
       LC_COLLATE = 'en_US.UTF-8'
       LC_CTYPE = 'en_US.UTF-8'
       CONNECTION LIMIT = -1;", databaseName );
               command.ExecuteNonQuery();
            }
            else
            {
               _log.WriteWarning( "Cannot create database '{0}', it already exists.", databaseName );
            }
         }
      }

      private string GetDatabaseFromConnectionString( DbConnectionStringBuilder cnsBuilder )
      {
         var database = cnsBuilder["database"] as string;
         if ( string.IsNullOrEmpty( database ) )
         {
            throw new ArgumentException(
               "Expected target database to be specified in the connection string but property 'database' was not found or empty." );
         }
         return database;
      }
   }
}
